COLS = 40
ROWS = 20

function deepCopy (to_copy) {
    var new_obj = new Object();
    if (typeof(to_copy) == 'object') {
        if (typeof(to_copy.length) != 'undefined')
            var new_obj = new Array();
        for (var child_obj in to_copy) {    
            if (typeof(to_copy[child_obj]) == 'object') {
                new_obj[child_obj] = deepCopy(to_copy[child_obj]);
            } else {
                new_obj[child_obj] = to_copy[child_obj];
            }
        }
    }
    return new_obj;
}

function LifeController($scope, $timeout) {
    function LifeCell(is_alive) {
        this.alive = is_alive;
    }

    function make_init_board() {
        var board = new Array();
        for (var i = 0; i < ROWS; ++i) {
            board.push([]);
            for (var j = 0; j < COLS; ++j) {
                var el = new LifeCell(Math.floor(Math.random()*4) == 0);
                board[i].push(el);
            }
        }
        return board;
    }

    init = function() {
        $scope.board_history = new Array();
        var new_board = make_init_board();
        iterate_board(new_board, new_board, "projected_alive");
        $scope.board_history.push(deepCopy(new_board));
        $scope.display_board = $scope.board_history[0];
        $scope.display_board.is_displayed = true;

        // Optional auto-advance logic.
        loop = function() {$scope.advance();$timeout(loop, 400);}
        // loop();
    }

    neighbor_at_loc = function(board, i, j) {
        if (i < 0 || j < 0 || i >= ROWS || j >= COLS) return 0;
        if (board[i][j].alive) return 1;
        return 0;
    }

    count_neighbors = function(board, i, j) {
        var neighbor_cnt = 0;
        for (var i_offset = -1; i_offset <= 1; ++i_offset)
            for (var j_offset = -1; j_offset <=1; ++j_offset) {
                if (i_offset != 0 || j_offset != 0)
                    neighbor_cnt += neighbor_at_loc(board,
                                                    i+i_offset, j+j_offset);
            }
        return neighbor_cnt;
    }

    iterate_board = function(current_board, next_board, alive_var) {
        for (var i = 0; i < ROWS; ++i) {
            for (var j = 0; j < COLS; ++j) {
                var neighbor_cnt = count_neighbors(current_board, i, j);
                if (neighbor_cnt < 2 || neighbor_cnt >= 4) {
                    next_board[i][j][alive_var] = false;
                } else if (neighbor_cnt == 2) {
                    // stays same
                } else if (neighbor_cnt == 3) {
                    next_board[i][j][alive_var] = true;
                }
            }
        }
    }

    newest_board = function() {
        return $scope.board_history[$scope.board_history.length - 1];
    }

    $scope.advance = function() {
        // Copy the old board for its 2d array structure - everything will be replaced
        var old_board = newest_board();
        var new_board = deepCopy(old_board);

        // update the board by changing "alive" in each cell.
        iterate_board(old_board, new_board, "alive");
        // store the expected state of the next iteration in "projected_alive".
        iterate_board(new_board, new_board, "projected_alive");
        $scope.board_history.push(new_board);

        $scope.display_board.is_displayed = false;
        $scope.display_board = new_board;
        $scope.display_board.is_displayed = true;
    }

    $scope.toggle = function(obj) {
        obj.alive = !obj.alive;
        // update the projection for the next turn based on the toggle.
        // TODO: only need to update this cell and its neighbors.
        iterate_board($scope.display_board, $scope.display_board, "projected_alive");
    }

    $scope.change_display = function(board_to_display) {
        $scope.display_board.is_displayed = false;
        $scope.display_board = board_to_display;
        board_to_display.is_displayed = true;
    }

    init();
}

var LifeModule = angular.module('LifeModule', []);

LifeModule.filter('gen_id', function() {
    return function(cell) {
        if (cell.alive && cell.projected_alive) return 'alive';
        if (cell.alive && !cell.projected_alive) return 'will_die';
        if (!cell.alive && cell.projected_alive) return 'will_live';
        return 'empty';
    }
});

LifeModule.filter('display_cell', function() {
    return function(cell, show_projected_as_alive) {
	return (cell.alive || (show_projected_as_alive && cell.projected_alived)) ? "@" : ".";
    }
});

LifeModule.filter('history_id', function() {
    return function(cell) {
        if (cell.alive) return 'alive';
        return 'empty';
    }
});

LifeModule.filter('history_border_color', function() {
    return function(board) {
        if (board.is_displayed) return '#FF0000';
        return '#000000';
    }
});
